///////////////////////////////////////////////////////////////////////////////
//
//
//
///////////////////////////////////////////////////////////////////////////////

package model
{
    import flash.events.Event;

    import model.service.IElfinService;

    import model.service.LogService;
    import model.vo.ConfigVO;
    import model.vo.LogRecord;
    import model.vo.LogUpdateToken;
    import model.vo.UserVO;

    import mx.utils.ObjectUtil;

    import org.kolonitsky.alexey.debug.DebugWrapper;
    import org.kolonitsky.alexey.utils.DateUtil;


    /**
     *  Proxy for manage log records. Load it and access.
     */
    public class LogProxy extends ElfinSafeProxy
    {
        public static const NAME:String = "logProxy";

        public static const UPDATE:String = "update_logProxy";
        public static const UPDATE_BALANCE:String = "updateBalance_logProxy";

        public static const NOT_ENOUGH_RECORDS:String = "notEnoughRecords_logProxy";
        public static const LOG_IS_EMPTY:String = "logIsEmpty_logProxy";




        //---------------------------------------------------------------------
        //
        //  Public
        //
        //---------------------------------------------------------------------

        //---------------------------------------------------------------------
        //  Properties
        //---------------------------------------------------------------------

        /**
         * Log is reloaded after success operation if this field is true;
         */
        public var reloadLogOnSuccess:Boolean = false;


        /**
         *  All records stored in proxy.
         */
        public function get records():Array
        {
            return data as Array
        }

        //---------------------------------------------------------------------
        //  Methods
        //---------------------------------------------------------------------

        /**
         * Constructor.
         * @param config is a global configuration object.
         */
        public function LogProxy(config:ConfigVO)
        {
            super(NAME, [], config)
        }

        /**
         *  Get record set for range
         *  @return
         */
        public function getLogRange(...args:Array):Array
        {
            // Transform any input parameters to beginDate and endDate.
            if (args.length == 1 && (args[0] is String))
            {
                _humanRange = args[0];
            }
            else if (args.length == 2 && (args[0] is Date) && (args[1] is Date))
            {
                _endDate = args[1];
                _beginDate = args[0];
            }
            else
            {
                throw new ArgumentError("Incorrect argument count of arguments type.");
            }

            if (records == null || records.length == 0)
            {
                var serv:LogService =  createLogService(getRecords_completeHandler);
                serv.loadRecords();
                return null;
            }

            sendUpdateNotification(LogUpdateToken.LOAD);
            return filterRecordsByRange();
        }

        /**
         *  This method enrich user value object by statistic information from
         *  records.
         *
         *  Collected info
         *
         *  <ul>
         *      <li>usedCurrencies &mdash; currencies used in user log</li>
         *      <li>usedTags &mdash; all tags used in user log</li>
         *      <li>usedWallets &mdash; wallets used by user </li>
         *      <li>createAccountDate &mdash; it is a first record creation date.</li>
         *  <ul>
         *
         *  This method invoked by CollectUserStatisticCommand
         *
         *  @see model.vo.UserVO
         *  @see controller.CollectUserStatisticCommand
         */
        public function fillUserStatisticInfo(user:UserVO):void
        {
            if (records == null || records.length == 0)
                return;

            this._user = user;
            this._user.usedCurrencies = {};
            this._user.usedTags = {};
            this._user.usedWallets = {};
            this._user.createAccountDate = (records[0] as LogRecord).date;

            records.forEach(collectStatistic);

            DebugWrapper.trace("INFO: used currencies: " + ObjectUtil.toString(user.usedCurrencies));
            DebugWrapper.trace("INFO: used tags: " + ObjectUtil.toString(user.usedTags));
            DebugWrapper.trace("INFO: used wallets: " + ObjectUtil.toString(user.usedTags));
            DebugWrapper.trace("INFO: account create date: " + this._user.createAccountDate);

            var balance:Object = {};
            for (var p:String in _user.usedWallets)
                balance[p] = (_user.usedWallets[p] as LogRecord).clone();
            sendNotification(UPDATE_BALANCE, balance);
        }

        /**
         * Update user statistic for one record
         * @param record
         * @param index
         */
        public function addStatisticFor(record:LogRecord, index:int):void
        {
            if (_user == null)
            {
                log("Command CollectStatistic must be invoked before adding record to user statistic.", 2);
                return;
            }

            collectStatistic(record, index, records);

            var balance:Object = {};
            for (var p:String in _user.usedWallets)
                balance[p] = (_user.usedWallets[p] as LogRecord).clone();
            sendNotification(UPDATE_BALANCE, balance);
        }

        /**
         * Remove user statistic for one record.
         * @param record
         * @param index
         */
        public function removeStatisticFor(record:LogRecord, index:int):void
        {
            if (_user == null)
            {
                log("Command CollectStatistic must be invoked before excluding record from user statistic.", 2);
                return;
            }

            removeStatistic(record, index,  records);

            var balance:Object = {};
            for (var p:String in _user.usedWallets)
                balance[p] = (_user.usedWallets[p] as LogRecord).clone();
            sendNotification(UPDATE_BALANCE, balance);
        }

        /**
         *  Tweet new record. Respons to this request is new LogRecord created on
         *  server side and saved to database.
         *
         *  @param tweetString
         */
        public function addRecord(tweetString:String):void
        {
            var serv:LogService = createLogService(addRecord_completeHandler);
            serv.tweet(tweetString);
        }

        /**
         *  Update passed record.
         *  @param record
         */
        public function updateRecord(record:LogRecord):void
        {
            var serv:LogService = createLogService(updateRecord_completeHandler);
            serv.updateRecord(record);
        }

        /**
         *  Delete passed record
         *  @param record
         */
        public function deleteRecord(record:LogRecord):void
        {
            var serv:LogService = createLogService(deleteRecord_completeHandler);
            serv.deleteRecord(record);
        }


        //-------------------------------------------------------------------------
        //
        //  Private
        //
        //-------------------------------------------------------------------------

        private var _humanRange:String = "";
        private var _beginDate:Date;
        private var _endDate:Date;

        private function createLogService(resultHandler:Function):LogService
        {
            var serv:IElfinService = createElfinService(LogService, resultHandler);
            return serv as LogService;
        }

        /**
         *  Get records range from beginDate to endDate
         *  @return Array of LogRecord
         */
        private function filterRecordsByRange():Array
        {
            if (records.length == 0)
                return [];

            // Translate humanRange to
            if (_humanRange)
            {
                // Minimal records requairemnt
                if (records.length < Config.ANALYTIC_RECORD_LIMIT)
                {
                    _humanRange = "all";
                    sendNotification(NOT_ENOUGH_RECORDS, records.length);
                }

                // All records from log.
                if (_humanRange == "all")
                {
                    _humanRange = "";
                    _beginDate = new Date(0);
                    _endDate = new Date();
                    return records.slice();
                }
                _endDate = (records[records.length - 1] as LogRecord).date;
                _beginDate = new Date(_endDate.time - DateUtil.humanTimeToMiliseconds(_humanRange));
                _humanRange = "";
            }

            var result:Array = [];
            var n:int = records.length;
            for (var i:int = 0; i < n; i++)
            {
                var recordTime:Number = (records[i] as LogRecord).date.time;
                if (recordTime > _beginDate.time && recordTime < _endDate.time)
                    result.push(records[i])
            }

            return result;
        }

        /**
         * Get record index in dictionary. Record finded by field
         *
         * @param record
         * @return index of record in dictionary. If record not found returned null;
         */
        private function getRecordIndex(record:LogRecord):int
        {
            if (record == null || records == null)
            {
                log("getRecordIndex. record = " + record + ", records = " + records);
                return -1;
            }

            // Search record by ID
            var n:int = records.length;
            for (var i:int = 0; i < n; i++)
                if (record.id == (records[i] as LogRecord).id)
                    return i;

            return -1;
        }


        /**
         * Send notification about update record.
         *
         * @param type
         * @param record
         * @param index
         */
        private function sendUpdateNotification(type:String,
            record:LogRecord=null, index:int = -1):void
        {
            var token:LogUpdateToken = new LogUpdateToken();
            token.type = type;
            token.list = filterRecordsByRange();

            if (record)
            {
                token.index = index == -1 ? getRecordIndex(record) : index;
                token.item = record;
            }

            sendNotification(UPDATE, token);
        }


        private function incCounter(object:Object,  key:String):void
        {
            if (key in object)
                object[key]++;
            else
                object[key] = 1;
        }

        private function decCounter(object:Object,  key:String):void
        {
            if (key in object)
            {
                if (object[key] == 1)
                    delete object[key];
                else
                    object[key]--
            }
        }


        private var _user:UserVO = null;

        /**
         * Collect statistic for one record.
         *
         * @param record
         * @param index
         * @param array
         */
        private function collectStatistic(record:LogRecord, index:int, array:Array):void
        {
            var i:int = 0;

            for (var currency:String in record.value)
                incCounter(_user.usedCurrencies, currency);

            var recordTags:Array = record.tags;
            for (i = 0; i < recordTags.length; i++)
                incCounter(_user.usedTags, recordTags[i]);

            var wallets:Array = record.wallets;
            for (i = 0; i < wallets.length; i++)
            {
                if (wallets in _user.usedWallets)
                    (_user.usedWallets[wallets[i]] as LogRecord).addValue(record);
                else
                    _user.usedWallets[wallets[i]] = record.clone();
            }
        }

        /**
         * Remove statistic for one record.
         *
         * @param record
         * @param index
         * @param array
         */
        private function removeStatistic(record:LogRecord,  index:int,  array:Array):void
        {
            var i:int = 0;

            for (var currency:String in record.value)
                decCounter(_user.usedCurrencies, currency);

            var recordTags:Array = record.tags;
            for (i = 0; i < recordTags.length; i++)
                decCounter(_user.usedCurrencies, recordTags[i]);

            var wallets:Array = record.wallets;
            for (i = 0; i < wallets.length; i++)
            {
                var r:LogRecord = wallets in _user.usedWallets ? _user.usedWallets[wallets[i]] : null;
                if (r)
                {
                    if (r.amount == 0.0)
                        delete _user.usedWallets[wallets[i]];
                    else
                        r.subValue(record.value);
                }
            }
        }



        //---------------------------------------------------------------------
        // Event Handlers
        //---------------------------------------------------------------------

        private function getRecords_completeHandler(event:Event):void
        {
            var serv:LogService = event.currentTarget as LogService;
            serv.removeEventListener(Event.COMPLETE, getRecords_completeHandler);

            data = serv.log;
            serv.clear();

            if (records.length == 0)
                sendNotification(LOG_IS_EMPTY);
            else
                sendUpdateNotification(LogUpdateToken.LOAD);
        }

        private function addRecord_completeHandler(event:Event):void
        {
            var serv:LogService = event.currentTarget as LogService;
            var record:LogRecord = serv.record;
            serv.clear();

            if (reloadLogOnSuccess)
            {
                serv =  createLogService(getRecords_completeHandler);
                serv.loadRecords();
                return;
            }

            if (record)
            {
                var index:int = records.length;
                records.push(record);
                addStatisticFor(record, index);
                sendUpdateNotification(LogUpdateToken.ADD, record, index);
            }
            else
            {
                log("Record not added.", 2);
            }
        }

        private function deleteRecord_completeHandler(event:Event):void
        {
            var serv:LogService = event.currentTarget as LogService;
            var record:LogRecord = serv.deletedRecord;
            serv.clear();

            var index:int = getRecordIndex(record);

            removeStatisticFor(record, index);
            records.splice(index, 1);

            sendUpdateNotification(LogUpdateToken.DELETE, record, index);
        }

        private function updateRecord_completeHandler(event:Event):void
        {
            var serv:LogService = event.currentTarget as LogService;
            var record:LogRecord = serv.updatedRecord;
            serv.clear();

            var index:int = getRecordIndex(record);

            removeStatisticFor(record, index);
            records[index] = record;
            addStatisticFor(record, index);

            sendUpdateNotification(LogUpdateToken.UPDATE, record, index);
        }
    }
}
